home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1991-12-05 | 22.1 KB | 809 lines | [ TEXT/PJMM]
unit Chess; { ©1991 [RHS] and Quinn "The Eskimo" } interface uses Sound, {} QuickDrawRules, {} GameTypes, {} NumSubs, Debugs, Failure, DialogSubs, {} ChessTypes, GraphSubs, ChessSubs, ChessMoves, ChessBoardSubs, ChessDrawing; procedure Main (var gameevent: gameEventRecord); implementation procedure Main (var gameevent: gameEventRecord); procedure InitRuleBook; function GetPict (id: integer): handle; var tmp: handle; begin tmp := GetResource('PICT', id); FailResError('PICT'); FailNil(tmp, 'PICT nil'); GetPict := tmp; end; { GetPict } function GetSnd (name: str255): handle; var tmp: handle; begin SetResLoad(false); tmp := GetNamedResource('snd ', name); SetResLoad(true); FailResError('snd'); FailNil(tmp, 'snd nil'); GetSnd := tmp; end; { GetSnd } var col: cellColour; piece: pieceType; h: Handle; gp: globalsPeek; dlog: DialogTHndl; begin gameevent.globals := NewHandle(sizeof(globalsRecord)); HLock(gameevent.globals); gp := globalsPeek(gameevent.globals); for col := Cwhite to Cblack do begin for piece := Oempty to OqueenW do begin h := GetResource('ICN#', rIconBase + 100 * ord(col) + ord(piece)); gp^^.icons[col, piece] := h; FailResError(concat('ICN# col=', DecStr(ord(col)), ' piece=', DecStr(ord(piece)))); FailNil(h, 'ICN# is nil'); end; { for } end; { for } gp^^.pawnpieces[kPawningX] := OqueenW; gp^^.pawnpieces[kPawningX + 1] := ObishopW; gp^^.pawnpieces[kPawningX + 2] := OknightW; gp^^.pawnpieces[kPawningX + 3] := OrookW; gp^^.waitpicts[Pwhite] := GetPict(rWaitForWhite); gp^^.waitpicts[Pblack] := GetPict(rWaitForBlack); gp^^.movepicts[Pwhite] := GetPict(rMoveWhite); gp^^.movepicts[Pblack] := GetPict(rMoveBlack); gp^^.resignpicts[Pwhite] := GetPict(rResignWhite); gp^^.resignpicts[Pblack] := GetPict(rResignBlack); gp^^.winpicts[Pwhite] := GetPict(rWinWhite); gp^^.winpicts[Pblack] := GetPict(rWinBlack); gp^^.pawnpicts[Pwhite] := GetPict(rPawnWhite); gp^^.pawnpicts[Pblack] := GetPict(rPawnBlack); gp^^.deadicons := GetResource('SICN', rDeadIcons); gp^^.checksnd := GetSnd('Check'); gp^^.matesnd := GetSnd('Mate'); FailResError('SICN 1'); FailNil(gp^^.deadicons, 'SICN nil 1'); gp^^.chessmenu := GetMenu(rChessMenu); FailNil(gp^^.chessmenu, 'Couldnt get menu'); dlog := DialogTHndl(GetResource('DLOG', rGameDialog)); FailResError('Game dialog'); FailNil(dlog, 'Game dialog handle'); gameevent.int1 := dlog^^.boundsRect.right - dlog^^.boundsRect.left; gameevent.int2 := dlog^^.boundsRect.bottom - dlog^^.boundsRect.top; ReleaseResource(Handle(dlog)); HUnlock(gameevent.globals); end; { InitRuleBook } procedure FinishRuleBook; begin DisposeHandle(gameevent.globals); gameevent.globals := nil; { Don't bother releasing resources cause GameMaster closes the game resource fork } end; { FinishRuleBook } procedure PseudoMain (var game: gameRecord; var globals: globalsRecord); procedure Resign; begin game.playstate := ps_GameOver; game.resigned := true; end; { Resign } procedure MovePiece (from, toc: boardCoord); procedure AddToMorgue (piece: pieceType); var p: playerType; r: Rect; itm: integer; body: morgueNdx; begin if piece <> Oempty then begin p := PieceToPlayer(piece); if p = Pwhite then begin itm := dit_wht_pieces end else begin itm := dit_blk_pieces; end; { if } GetDRect(game.dlg, itm, r); InvalRect(r); for body := 1 to kMorgueMax do begin if game.deaths[p][body] = Oempty then begin game.deaths[p][body] := piece; Exit(AddToMorgue); end; { if } end; { for } end; { if } end; { AddToMorgue } procedure FlashAndUpdatePair (from, toc: boardCoord); var i: integer; junk: longint; fromr, tor: Rect; begin game.board[toc.x, toc.y].occupant := game.board[from.x, from.y].occupant; game.board[from.x, from.y].occupant := Oempty; GetRect(game, from, fromr); GetRect(game, toc, tor); for i := 1 to GetMenuFlash do begin InvertRect(fromr); InvertRect(tor); Delay(6, junk); InvertRect(fromr); InvertRect(tor); Delay(6, junk); end; { for } PlotIcon(tor, globals.icons[game.board[toc.x, toc.y].colour, game.board[toc.x, toc.y].occupant]); PlotIcon(fromr, globals.icons[game.board[from.x, from.y].colour, game.board[from.x, from.y].occupant]); end; { FlashPair } procedure CompleteCastle; var rooksrc, rookdest: boardCoord; begin rooksrc.y := toc.y; rookdest.y := toc.y; case toc.x of 2: begin rooksrc.x := 0; rookdest.x := 3; end; 6: begin rooksrc.x := 7; rookdest.x := 5; end; otherwise Failure('king castled to illegal location'); end; { case } FlashAndUpdatePair(rooksrc, rookdest); end; { CompleteCastle } var tmpfrom: boardCoord; r: Rect; srcrect, dstrect: Rect; begin UpdateState(game.specialstate, game.board, from, toc); if (game.board[from.x, from.y].occupant in [OpawnB, OpawnW]) and (abs(from.x - toc.x) = abs(from.y - toc.y)) and (game.board[toc.x, toc.y].occupant = Oempty) then begin { Move evil pawn back so it can be taken } tmpfrom.x := toc.x; tmpfrom.y := from.y; FlashAndUpdatePair(tmpfrom, toc); end; { if } AddToMorgue(game.board[toc.x, toc.y].occupant); FlashAndUpdatePair(from, toc); if (game.board[toc.x, toc.y].occupant in [OkingW, OkingB]) and (abs(from.x - toc.x) > 1) then begin { Move rook to complete castle } CompleteCastle; end; { if } if (game.board[toc.x, toc.y].occupant in [OpawnW, OpawnB]) and (toc.y = BaseRow(Opposite(game.playertomove))) then begin { Pawn at last line } game.playstate := ps_Pawning; game.pawnpos := toc; GetPawningRect(game, dstrect); GetRect(game, toc, srcrect); ZoomRect(srcrect, dstrect, QDGlobals^.grey); InvalRect(dstrect); end;{ if } gameevent.modified := true; end; { MovePiece } procedure MoveComplete; procedure PlaySnd (snd: Handle); var state: SignedByte; err: OSErr; volume: integer; begin if game.sounds then begin GetSoundVol(volume); if volume = 0 then begin SysBeep(10); { Flashes the menu bar } end else begin LoadResource(snd); if ResError = noErr then begin FailResError('Could not load sound'); state := HGetState(snd); HLock(snd); err := SndPlay(nil, snd, true); FailOSError(err, 'snd play failed'); HSetState(snd, state); end else begin SysBeep(10); end; { if } end; { if} end; { if } end; { PlaySnd } var king: boardCoord; count: integer; valid: boardSet; begin if game.playstate = ps_Playing then begin { Only do checking if were not still waiting for PawnChoice } game.playertomove := Opposite(game.playertomove); if not FindPiece(PlayerToKing(game.playertomove), game.board, king) then begin Failure('Cant find king'); end; { if } if CheckCheck(game.board, king) then begin CalculateValidSet(game.board, game.specialstate, king, valid, count); if count = 0 then begin if NoValidMoves(game.playertomove, game.specialstate, game.board) then begin game.playstate := ps_GameOver; end; { if } end; { if } if game.playstate = ps_GameOver then begin PlaySnd(globals.matesnd); end else begin PlaySnd(globals.checksnd); end; { if } end; { if } end; { if } end; { MoveComplete } procedure PawnChoice (choice: pieceType; where: boardCoord); var srcrect, dstrect: Rect; junk: longint; i: integer; begin game.board[where.x, where.y].occupant := choice; game.playstate := ps_Playing; GetPawningRect(game, srcrect); GetRect(game, where, dstrect); ZoomRect(srcrect, dstrect, QDGlobals^.grey); InvalRect(srcrect); for i := 1 to GetMenuFlash do begin InvertRect(dstrect); Delay(6, junk); InvertRect(dstrect); Delay(6, junk); end; { for } PlotIcon(dstrect, globals.icons[game.board[where.x, where.y].colour, game.board[where.x, where.y].occupant]); end; { PawnChoice } procedure UpdateMyTurn; procedure SetStatus; var oldpic, pic: Handle; k: integer; r: Rect; begin GetDKind(game.dlg, dit_status, k); GetDHandle(game.dlg, dit_status, oldpic); case game.playState of ps_Pawning: case game.connectionstate of cs_Local: pic := globals.pawnpicts[game.playertomove]; cs_Remote: if gameevent.myturn then begin pic := globals.pawnpicts[game.playertomove]; end else begin pic := globals.waitpicts[game.playertomove]; end; { if } end; { case } ps_Playing: case game.connectionstate of cs_Local: pic := globals.movepicts[game.playertomove]; cs_Remote: if gameevent.myturn then begin pic := globals.movepicts[game.playertomove]; end else begin pic := globals.waitpicts[game.playertomove]; end; { if } end; { case } ps_GameOver: if game.resigned then begin pic := globals.resignpicts[game.playertomove]; end else begin pic := globals.winpicts[Opposite(game.playertomove)]; end; { if } end; { case } if pic <> oldpic then begin SetDHandle(game.dlg, dit_status, pic); GetDRect(game.dlg, dit_status, r); InvalRect(r); end; { if } end; { SetStatus } begin case game.connectionstate of { Not efficient but clear } cs_Local: gameevent.myturn := true; cs_Remote: gameevent.myturn := (game.playertomove = game.localplayer); end; { case } SetStatus; end; { UpdateMyTurn } procedure CommonInit; var r: rect; begin game.globals := globalsPeek(gameevent.globals); game.connectionstate := cs_Local; GetPort(game.dlg); WindowPeek(game.dlg)^.refcon := longint(gameevent.game); GetDRect(game.dlg, dit_board, r); game.boardXOrg := r.left; game.boardYOrg := r.top; SetDHandle(game.dlg, dit_line, Handle(@LineUserItem)); SetDHandle(game.dlg, dit_board, Handle(@BoardUserItem)); SetDHandle(game.dlg, dit_blk_pieces, Handle(@MorgueUserItem)); SetDHandle(game.dlg, dit_wht_pieces, Handle(@MorgueUserItem)); end; { CommonInit } procedure InitGameState; var p: playerType; m: morgueNdx; begin InitBoard(game.board); game.playertomove := Pwhite; game.playstate := ps_Playing; InitState(game.specialstate); for p := Pblack to Pwhite do begin for m := 1 to kMorgueMax do begin game.deaths[p, m] := Oempty; end; { for } end; { for } game.resigned := false; end; { InitGameState } procedure NewGame; begin game.showthemoves := false; game.sounds := true; game.localplayer := Pwhite; InitGameState; CommonInit; UpdateMyTurn; end; { NewGame } procedure OldGame; begin CommonInit; UpdateMyTurn; end; { OldGame } procedure Swap; begin game.localplayer := Opposite(game.localplayer); UpdateMyTurn; end; procedure Restart; begin InitGameState; InvalRect(game.dlg^.portRect); UpdateMyTurn; end; { Restart } procedure Activate; begin InsertMenu(globals.chessmenu, 0); DrawMenuBar; end; { Activate } procedure Deactivate; begin DeleteMenu(mid_chess); DrawMenuBar; end; { Deactivate } procedure UpdateMenus; procedure Greymenu (menu: MenuHandle; itm: integer; greyit: boolean); begin if greyit then begin DisableItem(menu, itm); end else begin EnableItem(menu, itm); end; { if } end; { GreyMenu } begin GreyMenu(globals.chessmenu, mit_show_moves, false); CheckItem(globals.chessmenu, mit_show_moves, game.showthemoves); CheckItem(globals.chessmenu, mit_sounds, game.sounds); GreyMenu(globals.chessmenu, mit_resign, not gameevent.myturn); end; { UpdateMenus } procedure SendResign; forward; procedure Menu; begin if gameevent.int1 <> mid_chess then begin Failure('Menu select not on my menu'); end; { if } case gameevent.int2 of mit_sounds: game.sounds := not game.sounds; mit_show_moves: game.showthemoves := not game.showthemoves; mit_resign: begin Resign; if game.connectionstate = cs_Remote then begin SendResign; end; { if } UpdateMyTurn; end; { if } end; { case } gameevent.modified := true; end; { Menu } procedure ConnectionLost; begin game.connectionstate := cs_Local; UpdateMyTurn; end; { ConnectionLost } procedure ConnectionMade; begin game.connectionstate := cs_Remote; UpdateMyTurn; end; { ConnectionMade } procedure MessageReceived; procedure DoRemoteMove; var toc, from: boardCoord; begin if game.playertomove = game.localplayer then begin Failure('The other player is trying to move'); end else begin if Length(gameevent.message) = 5 then begin from.x := ord(gameevent.message[2]) - ord('0'); from.y := ord(gameevent.message[3]) - ord('0'); toc.x := ord(gameevent.message[4]) - ord('0'); toc.y := ord(gameevent.message[5]) - ord('0'); MovePiece(from, toc); MoveComplete; UpdateMyTurn; end else begin Failure(concat('Message not right length', gameevent.message)); end; { if } end; { if } end; { DoRemoteMove } procedure DoRemoteResign; begin if game.playertomove = game.localplayer then begin Failure('The other player is trying to resign'); end else begin if Length(gameevent.message) = 1 then begin Resign; UpdateMyTurn; end else begin Failure(concat('Message not right length', gameevent.message)); end; { if } end; { if } end; { DoRemoteResign } procedure DoRemotePawnChoice; var where: boardCoord; choice: pieceType; begin if game.playertomove = game.localplayer then begin Failure('The other player is trying to choice a pawn'); end else if game.playstate <> ps_Pawning then begin Failure('Pawning with ps_Pawning'); end else if Length(gameevent.message) <> 4 then begin Failure(concat('Message not right length', gameevent.message)); end else begin choice := pieceType(ord(gameevent.message[2]) - ord('A')); where.x := ord(gameevent.message[3]) - ord('0'); where.y := ord(gameevent.message[4]) - ord('0'); PawnChoice(choice, where); MoveComplete; UpdateMyTurn; end; { if } end; { DoRemotePawnChoice } var ch: char; begin if game.connectionstate <> cs_Remote then begin Failure('Message from God: Stop playing with TCP'); end else begin ch := copy(gameevent.message, 1, 1); case ch of m_Move: DoRemoteMove; m_Resign: DoRemoteResign; m_PawnChoice: DoRemotePawnChoice; otherwise Failure(concat('Message not understood ', gameevent.message)); end; { case } end; end; { MessageReceived } procedure SetupSendMessage (message: str255); begin gameevent.message := message; gameevent.event := ge_SendMessage; end; { SetupSendMessage } procedure SendRemoteMove (c1, c2: boardCoord); begin SetupSendMessage(concat(m_Move, chr(c1.x + ord('0')), chr(c1.y + ord('0')), chr(c2.x + ord('0')), chr(c2.y + ord('0')))); end; { SendRemoteMove } procedure SendResign; begin SetupSendmessage(m_Resign); end; { SendResign } procedure SendPawnChoice (choice: pieceType; c1: boardCoord); begin SetupSendMessage(concat(m_PawnChoice, chr(ord('A') + ord(choice)), chr(c1.x + ord('0')), chr(c1.y + ord('0')))); end; { SendPawnChoice } procedure MouseDown; function DragPiece (start: boardCoord; var finish: boardCoord): boolean; procedure InvertValidSet (var valid: boardSet); var x: boardXNdx; y: boardYNdx; r: rect; where: boardCoord; begin for x := 0 to kBoardXMax do begin for y := 0 to kBoardYMax do begin if valid[x, y] then begin where.x := x; where.y := y; GetRect(game, where, r); InvertOval(r); end; { if } end; { for} end; { for } end; { InvertValidSet } const shift_key = 56; var valid: boardSet; ps: PenState; old: boardCoord; hirect, dragrect: Rect; oldmouse, newmouse: Point; onboard: boolean; debugging: boolean; km: KeyMap; count: integer; begin CalculateValidSet(game.board, game.specialstate, start, valid, count); if count = 0 then begin DragPiece := false; end else begin GetKeys(km); debugging := km[shift_key] or game.showthemoves; GetPenState(ps); PenMode(patXor); PenPat(QDGlobals^.grey); PenSize(2, 2); if debugging then begin InvertValidSet(valid); end; { if } GetRect(game, start, hirect); dragrect := hirect; FrameRect(dragrect); InvertRect(hirect); oldmouse := gameevent.where; old := start; finish := start; while Button do begin GetMouse(newmouse); if (oldmouse.h <> newmouse.h) or (oldmouse.v <> newmouse.v) then begin FrameRect(dragrect); OffsetRect(dragrect, newmouse.h - oldmouse.h, newmouse.v - oldmouse.v); FrameRect(dragrect); oldmouse := newmouse; onboard := FindCell(game, newmouse, finish); if not onboard or not valid[finish.x, finish.y] then begin finish := old; end; { if } if (finish.x <> old.x) or (finish.y <> old.y) then begin InvertRect(hirect); GetRect(game, finish, hirect); InvertRect(hirect); old := finish; end; { if } end; { if } end; { while } FrameRect(dragrect); InvertRect(hirect); if debugging then begin InvertValidSet(valid); end; { if } SetPenState(ps); DragPiece := (finish.x <> start.x) or (finish.y <> start.y); end; { if } end; { DragPiece } function DragPawnButtons (start: boardCoord; var choice: pieceType): boolean; var r: Rect; inverted: boolean; inacell: boolean; loc: boardCoord; mouse: Point; junk: longint; i: integer; begin GetRect(game, start, r); inverted := false; while button do begin GetMouse(mouse); inacell := FindCell(game, mouse, loc); inacell := inacell and (loc.x = start.x) and (loc.y = start.y); if (inacell and not inverted) or (inverted and not inacell) then begin InvertRect(r); inverted := not inverted; end; { if } end; { while } if inverted then begin for i := 1 to GetMenuFlash do begin InvertRect(r); Delay(6, junk); InvertRect(r); Delay(6, junk); end; { for } InvertRect(r); choice := globals.pawnpieces[loc.x]; end; { if } DragPawnButtons := inverted; end; { DragPawnButtons } var click, dest: boardCoord; inacell: boolean; choice: pieceType; begin click.x := 0; click.y := 0; dest.x := 0; dest.y := 0; case game.playState of ps_Pawning: begin inacell := FindCell(game, gameevent.where, click); if inacell and (click.y = kPawningY) and (click.x in [kPawningX..kPawningXMax]) then begin if DragPawnButtons(click, choice) then begin PawnChoice(PlayerPiece(game.playertomove, choice), game.pawnpos); if game.connectionstate = cs_Remote then begin SendPawnChoice(PlayerPiece(game.playertomove, choice), game.pawnpos); end; { if } MoveComplete; UpdateMyTurn; end; { if } end; { if } end; ps_Playing: begin inacell := FindCell(game, gameevent.where, click); if inacell and PieceInMyTeam(game.playertomove, game.board[click.x, click.y].occupant) then begin if DragPiece(click, dest) then begin MovePiece(click, dest); MoveComplete; if game.connectionstate = cs_Remote then begin SendRemoteMove(click, dest); end; { if } UpdateMyTurn; end; { if } end; { if } end; ps_GameOver: begin SysBeep(10); end; end; { case } end; { MouseDown } begin { PseudoMain } case gameevent.event of ge_NewGame: NewGame; ge_OldGame: OldGame; ge_Swap: Swap; ge_Restart: Restart; ge_Activate: Activate; ge_Deactivate: Deactivate; ge_UpdateMenus: UpdateMenus; ge_Menu: Menu; ge_ConnectionLost: ConnectionLost; ge_ConnectionMade: ConnectionMade; ge_MessageReceived: MessageReceived; ge_MouseDown: MouseDown; otherwise end; { case } end; { PseudoMain } var s1, s2: signedByte; s1valid, s2valid: boolean; begin { Main } case gameevent.event of ge_InitRuleBook: InitRuleBook; ge_FinishRuleBook: FinishRuleBook; otherwise begin if gameevent.event = ge_NewGame then begin SetHandleSize(gameevent.game, sizeof(gameRecord)); end; (* if *) s1valid := (gameevent.game <> nil); if s1valid then begin s1 := HGetState(gameevent.game); HLock(gameevent.game); end; (* if *) s2valid := (gameevent.globals <> nil); if s2valid then begin s2 := HGetState(gameevent.globals); HLock(gameevent.globals); end; (* if *) PseudoMain(gamePeek(gameevent.game)^^, globalsPeek(gameevent.globals)^^); if s1valid then begin HSetState(gameevent.game, s1); end; (* if *) if s2valid then begin HSetState(gameevent.globals, s2); end; (* if *) end; (* otherwise *) end; (* case *) end; { Main } end. { Chess }